/*
 * Copyright (c) 2011 Joe Kopena <tjkopena@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*/

package com.rocketshipgames.haxe.gfx.primitives;

import com.rocketshipgames.haxe.Context;

import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Matrix;

import com.rocketshipgames.haxe.gfx.Graphic;
import com.rocketshipgames.haxe.AssetCache;

class Spritesheet {

  // Should be inline functions to make these readonly properties.
  public var width:Int;
  public var height:Int;

  // In the future haXe will allow package and method level access
  // control (http://haxe.org/manual/acl) but in the interim these are
  // public to enable helper class access.
  public var _bitmap:BitmapData;
  public var _flipped:BitmapData;
  public var _mirrored:BitmapData;
  public var _inverted:BitmapData;

  public var _rows:Int;
  public var _cols:Int;

  public var _animations:Array<Animation>;

  public var _xoff:Float;
  public var _yoff:Float;

  public var _point:Point;
  public var _rect:Rectangle;

  public function new(path:String,
		      ?width_:Int=0, ?height_:Int=0,
		      ?flippable:Bool=false, ?mirrorable:Bool=false):Void
  {
    _bitmap = AssetCache.instance().getBitmapData(path);
    if (_bitmap == null) {
      trace("ERROR: Null bitmap data for '" + path + "'");
      return;
    }

    _animations = new Array();

    width = (width_==0)?_bitmap.width:width_;
    height = (height_==0)?_bitmap.height:height_;

    _cols = Std.int(_bitmap.width / width);
    _rows = Std.int(_bitmap.height / height);

    _point = new Point(0, 0);
    _rect = new Rectangle(0, 0, width, height);

    _xoff = -width/2.0;
    _yoff = -height/2.0;

    if (flippable)
      _flipped = transformBitmap(true, false);
    if (mirrorable)
      _mirrored = transformBitmap(false, true);
    if (flippable && mirrorable)
      _inverted = transformBitmap(true, true);

    trace("New '" + path + "': " + _cols + " cols, " + _rows + " rows" +
	  ((flippable || mirrorable)?"; ":"") +
	  ((flippable)?("flippable"+((mirrorable)?", ":"")):"") +
	  ((mirrorable)?"mirrorable":""));

    // end function new
  }

  //------------------------------------------------------------
  private function transformBitmap(flip:Bool, mirror:Bool):BitmapData
  {
    var res:BitmapData = new BitmapData(_bitmap.width, _bitmap.height, true, 0);
    var cell:BitmapData = new BitmapData(width, height, true, 0);

    var matrix:Matrix = new Matrix((flip)?-1:1, 0, 0, (mirror)?-1:1, 0, 0);
    // var matrix:Matrix = new Matrix(1, 0, 0, 1, 0, 0);

    var point:Point = new Point(0,0);
    var rect:Rectangle = new Rectangle(0, 0, width, height);
    for (r in 0..._rows) {
      for (c in 0..._cols) {

	rect.x = c * width;
	rect.y = r * height;

	matrix.tx = rect.x+((flip)?width:0);
	matrix.ty = rect.y+((mirror)?height:0);

	cell.fillRect(cell.rect,0x00000000);
	cell.copyPixels(_bitmap, rect,
			point, null, null, true);
	res.draw(cell, matrix, null, null, null, false);
      }
    }

    return res;
    // end function flipBitmap
  }

  //------------------------------------------------------------
  public function setOffset(x:Float, y:Float)
  {
    _xoff = x;
    _yoff = y;
    // end function setOffset
  }

  //------------------------------------------------------------
  public function setAnimation(id:Int, frames:Array<Int>,
			       loop:Bool=true,
			       interval:Int=100):Void
  {
    _animations[id] = new Animation(frames, loop, interval);
    // end function addAnimation
  }

  //------------------------------------------------------------
  public function newInstance():SpritesheetInstance
  {
    return new SpritesheetInstance(this);
    // end function newInstance
  }

  // end class Spritesheet
}

//------------------------------------------------------------
private class Animation {

  // In the future haXe will allow package and method level access
  // control (http://haxe.org/manual/acl) but in the interim these are
  // public to enable helper class access.
  // public var _id:Int;
  public var _frames:Array<Int>;
  public var _interval:Int;
  public var _loop:Bool;

  public function new(frames:Array<Int>, loop:Bool, interval:Int):Void
  {
    _frames = frames;
    _interval = interval;
    _loop = loop;
    // end function new
  }

  // end class Animation
}

//------------------------------------------------------------
class SpritesheetInstance implements Graphic {

  private var _spritesheet:Spritesheet;

  private var _animationIndex:Int;
  private var _animationFrame:Int;
  private var _frame:Int;

  private var _playing:Bool;

  private var _changeTimestamp:Int;

  public var flipped:Bool;
  public var mirrored:Bool;

  public function new(sheet:Spritesheet):Void
  {
    _spritesheet = sheet;
    _animationIndex = 0;
    _animationFrame = 0;
    _frame = 0;

    flipped = false;
    mirrored = false;

    _playing = false;

    // end function new
  }

  public function play(animationIndex:Int,
		       ?reset:Bool=false, ?frame:Int=0):Void
  {
    if (!reset && _playing && _animationIndex == animationIndex)
      return;

    _playing = true;
    _animationIndex = animationIndex;
    _animationFrame = frame;
    _changeTimestamp = Context.time;
    // end function play
  }

  public function stop():Void
  {
    _playing = false;
    _changeTimestamp = Context.time - _changeTimestamp;
  }

  public function resume():Void
  {
    _playing = true;
    _changeTimestamp = Context.time - _changeTimestamp;
  }

  public function setFrame(x:Int, y:Int=0):Void
  {
    _playing = false;
    _frame = (y * _spritesheet._cols) + x;
    // end function setFrame
  }

  //------------------------------------------------------------
  public function render(x:Float, y:Float):Void
  {
    if (_playing &&
	(Context.time - _changeTimestamp) >
	_spritesheet._animations[_animationIndex]._interval) {

      _animationFrame++;
      if (_animationFrame >=
	  _spritesheet._animations[_animationIndex]._frames.length) {
	if (_spritesheet._animations[_animationIndex]._loop) {
	  _animationFrame = 0;
	} else {
	  _playing = false;
	  _animationFrame--;
	}
      }

      _frame =
	_spritesheet._animations[_animationIndex]._frames[_animationFrame];
      _changeTimestamp = Context.time;
    }

    _spritesheet._point.x = x+_spritesheet._xoff;
    _spritesheet._point.y = y+_spritesheet._yoff;
    _spritesheet._rect.x = (_frame%_spritesheet._cols) * _spritesheet.width;
    _spritesheet._rect.y = Std.int(_frame/_spritesheet._cols) *
      _spritesheet.height;

    if (flipped && mirrored)
      Context.buffer.copyPixels(_spritesheet._inverted, _spritesheet._rect,
			      _spritesheet._point, null, null, true);
    else if (flipped)
      Context.buffer.copyPixels(_spritesheet._flipped, _spritesheet._rect,
			      _spritesheet._point, null, null, true);
    else if (mirrored)
      Context.buffer.copyPixels(_spritesheet._mirrored, _spritesheet._rect,
			      _spritesheet._point, null, null, true);
    else
      Context.buffer.copyPixels(_spritesheet._bitmap, _spritesheet._rect,
			      _spritesheet._point, null, null, true);

    // end function render
  }

  // end class SpritesheetInstance
}
